


#include "client_process.h"
#include "loop_event.hpp"
#include "message_attr.h"
#include "stun_message.h"
#include "turn_client_main/file_operation.h"
#include "turn_client_main/peer_client_process.h"
#include "turn_server/data/nonce_data.h"
#include "udp_client.h"
#include "write_buffer.h"
#include <arpa/inet.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <strings.h>
#include <sys/socket.h>
#include <unistd.h>

void SendMessage(LoopEvent *loop_event, UdpClient *client, StunMessage &message)
{
	WriteBuffer write_buffer;
	message.ComposeStunPacket(write_buffer);
	if (client)
	{
		client->DoSendMsg(write_buffer.Data(), write_buffer.Length());
	}
}

void SendMessage(LoopEvent *loop_event, UdpClient *client, PeerClientInformation & peer_information, uint8_t * data, int length)
{
	StunMessage new_message;
	new_message.SetMsgType(EmSendIndication);
	new_message.m_magic_cookie = magic_cookie;
	new_message.m_transaction_id = new_message.ProducerTransactionId();

	auto xor_peer_attr = new AttrXorMappedAddress();
	xor_peer_attr->SetAttrType(EmXorPeerAddress);


	struct sockaddr_in peer_addr;
	bzero(&peer_addr, sizeof(peer_addr));

	peer_addr.sin_port = htons(peer_information.m_srfix_port);
	peer_addr.sin_family = AF_INET;
	inet_pton(AF_INET, peer_information.m_srfix_ip.c_str(), &peer_addr.sin_addr);

	xor_peer_attr->ParseAddrInfo(IPPROTO_IP, (struct sockaddr*)&peer_addr, sizeof(peer_addr));

	new_message.AddStunAttr(xor_peer_attr);


	auto data_attr = new AttrData();

	data_attr->PushData((const uint8_t*)data, length);

	new_message.AddStunAttr(data_attr);

	SendMessage(loop_event, client, new_message);

}

void AllocateRequest(LoopEvent *loop_event, UdpClient *client, StunMessage &message)
{
	StunMessage allocate_message;
	allocate_message.SetMsgType(EmAllocateRequest);
	allocate_message.m_transaction_id = allocate_message.ProducerTransactionId();
	allocate_message.m_magic_cookie   = magic_cookie;

	SendMessage(loop_event, client, allocate_message);
}

void ProcessBindResponse(LoopEvent *loop_event, UdpClient *client, StunMessage &message)
{

	auto attr = message.m_attr_map.find(EmXorMappedAddress);

	if (attr != message.m_attr_map.end())
	{
		auto attr_data = (AttrXorMappedAddress*)attr->second.get();
		printf("IP : %s\tPORT : %d.\n", attr_data->DecoderAddr().c_str(), attr_data->DecoderPort());
		// todo allocate 
		AllocateRequest(loop_event, client, message);
	}
	else 
	{
		printf("no found EmXorMappedAddress type.\n");
	}

}

void ProcessAllocateErrorResponse(LoopEvent *loop_event, UdpClient *client, StunMessage &message, AllocateDataInformation & information)
{

	auto & attr_map = message.m_attr_map;

	printf("attr map size : %ld.\n", attr_map.size());

	auto nonce_attr_iter = attr_map.find(EmNONCE);
	if (nonce_attr_iter != attr_map.end() && nonce_attr_iter->second.get())
	{
		auto nonce_attr = (AttrNonce*)nonce_attr_iter->second.get();
		std::string nonce = nonce_attr->GetNonceData();
		printf("nonce data id : %s.\n", nonce.c_str());
		information.m_nonce_id = nonce;

		StunMessage allocate_message;
		allocate_message.SetMsgType(EmAllocateRequest);
		allocate_message.m_transaction_id = allocate_message.ProducerTransactionId();
		allocate_message.m_magic_cookie   = magic_cookie;

		AttrNonce * nonce_data = new AttrNonce();
		nonce_data->SetNonce(nonce);
		allocate_message.AddStunAttr(nonce_data);
	

		AttrUserName * attr_name = new AttrUserName();
		attr_name->SetUserName("helloworld");
		allocate_message.AddStunAttr(attr_name);

		AttrRealm * realm = new AttrRealm();
		realm->SetRealm("bilibili");
		allocate_message.AddStunAttr(realm);


		SendMessage(loop_event, client, allocate_message);
	}
	else 
	{
		printf("no found nonce data.\n");
	}
}


void ProcessAllocateSuccessResponse(LoopEvent *loop_event, UdpClient *client, StunMessage &message, AllocateDataInformation &information, PeerClientInformation & peer_information)
{
	auto attr_map = message.m_attr_map;

	printf("attr map size : %ld.\n", attr_map.size());

	auto relay_addr_iter = attr_map.find(EmXorRelayedAddress);


	if (relay_addr_iter != attr_map.end() && relay_addr_iter->second)
	{
		auto xor_relay_attr = (AttrXorMappedAddress*)relay_addr_iter->second.get();
		printf("allocate relay address : %s\tport : %d.\n", xor_relay_attr->DecoderAddr().c_str(), xor_relay_attr->DecoderPort());

		information.m_relay_addr = xor_relay_attr->DecoderAddr();
		information.m_relay_port = xor_relay_attr->DecoderPort();

		information.m_relay_sai.sin_family = AF_INET;
		information.m_relay_sai.sin_port   = htons(information.m_relay_port);
		inet_pton(AF_INET, information.m_relay_addr.c_str(), &information.m_relay_sai.sin_addr);

		information.m_allocate_success = true;

		auto life_time_attr = attr_map.find(EmLifeTime);
		if (life_time_attr != attr_map.end())
		{
			auto life_attr = (AttrLifeTime*)life_time_attr->second.get();
			information.m_lifetime = life_attr->GetLifeTime();
			if (information.m_lifetime > 60)
			{
				printf("origin life time : %d.\n", information.m_lifetime);
				information.m_lifetime = 30;
			}
		}
		else 
		{
			information.m_lifetime = 30;
		}
		// fresh lift time
		loop_event->PushAfterTask(information.m_lifetime * 1000, [loop_event, client, &information](){


			SendRefreshRequest(loop_event, client, information);
		});
		AddPermission(loop_event, client, information, peer_information);
	}
	else 
	{
		printf("no found xor relay address attr data.\n");
	}

}

void SendRefreshRequest(LoopEvent *loop_event, UdpClient *client, AllocateDataInformation &information)
{
	StunMessage refresh_message;
	refresh_message.SetMsgType(EmRefreshRequest);
	refresh_message.m_transaction_id = refresh_message.ProducerTransactionId();
	refresh_message.m_magic_cookie   = magic_cookie;

	auto nonce_attr = new AttrNonce();
	nonce_attr->SetNonce(information.m_nonce_id);

	refresh_message.AddStunAttr(nonce_attr);

	SendMessage(loop_event, client, refresh_message);

	printf("function : %s send refresh data message.\n", __func__);

	// fresh lift time
	loop_event->PushAfterTask(information.m_lifetime * 1000, [loop_event, client, &information](){


		SendRefreshRequest(loop_event, client, information);
	});
}

void AddPermission(LoopEvent *loop_event, UdpClient *client, AllocateDataInformation &information, PeerClientInformation & peer_information)
{
	if (peer_information.m_srfix_ip.empty())
	{

		loop_event->PushAfterTask(5 * 1000, [loop_event, client, &information, &peer_information](){

			AddPermission(loop_event, client, information, peer_information);

		});
	}
	else
	{
		struct sockaddr_in peer_addr;
		bzero(&peer_addr, sizeof(peer_addr));

		peer_addr.sin_port = htons(peer_information.m_srfix_port);
		peer_addr.sin_family = AF_INET;
		inet_pton(AF_INET, peer_information.m_srfix_ip.c_str(), &peer_addr.sin_addr);


		StunMessage message;
		message.SetMsgType(EmCreatePermission);
		message.m_transaction_id = message.ProducerTransactionId();
		message.m_magic_cookie   = magic_cookie;

		auto peer_attr = new AttrXorMappedAddress();
		peer_attr->SetAttrType(EmXorPeerAddress);
		peer_attr->ParseAddrInfo(IPPROTO_IP, (struct sockaddr*)&peer_addr, sizeof(struct sockaddr_in));
		message.AddStunAttr(peer_attr);

		auto nonce_attr = new AttrNonce();
		nonce_attr->SetNonce(information.m_nonce_id);
		message.AddStunAttr(nonce_attr);

		SendMessage(loop_event, client, message);
	}
} 


void AddPermissionSuccessResponse(LoopEvent *loop_event, UdpClient *client, StunMessage &message, AllocateDataInformation &information, PeerClientInformation & peer_information)
{
	peer_information.m_ip   = information.m_relay_addr;
	peer_information.m_port = information.m_relay_port;

	loop_event->PushTask([loop_event, &peer_information](){

		TestAllocate(loop_event, peer_information);
	});
	loop_event->PushAfterTask(3000, [loop_event, client, &information, &peer_information](){

        StunMessage msg;
		// SendTestData(loop_event, client, information, peer_information);
		SendFileContent(loop_event, client, information, peer_information);
        // ProcessDataIndication(loop_event, client, msg, information, peer_information);
	});
}


void SendTestData(LoopEvent *loop_event, UdpClient *client, AllocateDataInformation & information, PeerClientInformation & peer_information)
{
	StunMessage message;
	message.SetMsgType(EmSendIndication);
	message.m_magic_cookie = magic_cookie;
	message.m_transaction_id = message.ProducerTransactionId();

	auto xor_peer_attr = new AttrXorMappedAddress();
	xor_peer_attr->SetAttrType(EmXorPeerAddress);


	struct sockaddr_in peer_addr;
	bzero(&peer_addr, sizeof(peer_addr));

	peer_addr.sin_port = htons(peer_information.m_srfix_port);
	peer_addr.sin_family = AF_INET;
	inet_pton(AF_INET, peer_information.m_srfix_ip.c_str(), &peer_addr.sin_addr);

	xor_peer_attr->ParseAddrInfo(IPPROTO_IP, (struct sockaddr*)&peer_addr, sizeof(peer_addr));

	message.AddStunAttr(xor_peer_attr);


	auto data_attr = new AttrData();
	std::string data = "helloworld,turn server";
	data_attr->PushData((const uint8_t*)data.c_str(), data.size());

	message.AddStunAttr(data_attr);

	SendMessage(loop_event, client, message);
}

void SendFileContent(LoopEvent * loop_event, UdpClient * client, AllocateDataInformation & information, PeerClientInformation & peer_information)
{
    std::thread thd([loop_event, client, &information, &peer_information](){

        int size = 0;
        int index = 0;
        for (;(size = GetFileContent(information.m_file_operation)) > 0 && index < 4600; index++)
        {
        	uint32_t packet_index = information.m_file_operation.m_packet_index++;
            printf("packet seq index : %d.\n", packet_index);
            memcpy(information.m_file_operation.m_buffer, &packet_index, sizeof(packet_index));
            SendMessage(loop_event, client, peer_information, information.m_file_operation.m_buffer, size + sizeof(packet_index));
            size = 0;
            usleep(10000);
        }
    });
    thd.detach();
}

// for test 
void ProcessDataIndication(LoopEvent *loop_event, UdpClient *client, StunMessage &message, AllocateDataInformation &information, PeerClientInformation &peer_information)
{
	std::string data = "ABCDEFGHIJKLMNOPQRSTWXYZ.ABCDEFGHIJKLMNOPQRSTWXYZ.ABCDEFGHIJKLMNOPQRSTWXYZ.ABCDEFGHIJKLMNOPQRSTWXYZ.ABCDEFGHIJKLMNOPQRSTWXYZ.ABCDEFGHIJKLMNOPQRSTWXYZ.ABCDEFGHIJKLMNOPQRSTWXYZ.";

	StunMessage new_message;
	new_message.SetMsgType(EmSendIndication);
	new_message.m_magic_cookie = magic_cookie;
	new_message.m_transaction_id = new_message.ProducerTransactionId();

	auto xor_peer_attr = new AttrXorMappedAddress();
	xor_peer_attr->SetAttrType(EmXorPeerAddress);


	struct sockaddr_in peer_addr;
	bzero(&peer_addr, sizeof(peer_addr));

	peer_addr.sin_port = htons(peer_information.m_srfix_port);
	peer_addr.sin_family = AF_INET;
	inet_pton(AF_INET, peer_information.m_srfix_ip.c_str(), &peer_addr.sin_addr);

	xor_peer_attr->ParseAddrInfo(IPPROTO_IP, (struct sockaddr*)&peer_addr, sizeof(peer_addr));

	new_message.AddStunAttr(xor_peer_attr);


	auto data_attr = new AttrData();

	printf("data length %ld.\n", data.size());

	data_attr->PushData((const uint8_t*)data.c_str(), data.size());

	new_message.AddStunAttr(data_attr);

	SendMessage(loop_event, client, new_message);

    loop_event->PushAfterTask(8000, [loop_event, client, &information, &peer_information](){

        StunMessage msg;
        // SendTestData(loop_event, client, information, peer_information);
        // SendFileContent(loop_event, client, information, peer_information);
        ProcessDataIndication(loop_event, client, msg, information, peer_information);
    });
}


